﻿using System.Collections.Generic;
using System.Data.Entity.Extensions.UnitOfWork;
using System.Linq;
using System.Linq.Expressions;

namespace System.Data.Entity.Extensions.Repository
{
    /// <summary>
    /// 
    /// </summary>
    /// <typeparam name="TEntity"></typeparam>
    public abstract class DbContextRepository<TEntity> : IDbContextRepository<TEntity> where TEntity : class
    {
        #region Constructor 

        /// <summary>
        /// 
        /// </summary>
        protected DbContextRepository()
        {

        }           
        
        #endregion

        #region IDisposable Implementation

        private bool _disposed;

        /// <summary>
        /// 
        /// </summary>
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="disposing"></param>
        protected virtual void Dispose(bool disposing)
        {
            if (!_disposed)
            {
                if (disposing)
                {

                }

                _disposed = true;
            }
        }

        /// <summary>
        /// 
        /// </summary>
        ~DbContextRepository()
        {
            Dispose(false);
        }

        #endregion

        #region IRepository<TEntity> Implementation

        #region UnitOfWork

        /// <summary>
        /// 
        /// </summary>
        private DbContextUnitOfWork _unitOfWork;

        /// <summary>
        /// 
        /// </summary>
        public DbContextUnitOfWork UnitOfWork
        {
            get
            {
                if (this._unitOfWork == null)
                {
                    this._unitOfWork = (DbContextUnitOfWork)Extensions.UnitOfWork.UnitOfWork.Current;
                }

                return this._unitOfWork;
            }
        }

        #endregion

        #region Context

        /// <summary>
        /// 
        /// </summary>
        private DbContext _context;

        /// <summary>
        /// 
        /// </summary>
        public DbContext Context
        {
            get
            {
                if (this._context == null)
                {
                    this._context = this.UnitOfWork.Context;
                }
                
                return this._context;
            }
        }

        #endregion

        #region Set

        /// <summary>
        /// 
        /// </summary>
        private DbSet<TEntity> _set;

        /// <summary>
        /// 
        /// </summary>
        public DbSet<TEntity> Set
        {
            get
            {                
                if (this._set == null)
                {
                    this._set = this.Context.Set<TEntity>();
                }

                return this._set;
            }
        }

        #endregion

        #region Create

        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public virtual TEntity Create()
        {
            try
            {
                TEntity entity = this.Set.Create();
                this.OnCreated(new ActionEventArgs<TEntity>(entity));

                return entity;
            }
            catch (Exception)
            {
                throw;
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="TDerivedEntity"></typeparam>
        /// <returns></returns>
        public virtual TDerivedEntity Create<TDerivedEntity>() where TDerivedEntity : class, TEntity
        {
            try
            {
                TDerivedEntity derivedEntity = this.Set.Create<TDerivedEntity>();
                this.OnCreated(new ActionEventArgs<TEntity>(derivedEntity as TEntity));

                return derivedEntity;
            }
            catch (Exception)
            {
                throw;
            }
        }
                
        /// <summary>
        /// 
        /// </summary>
        public event ActionEventHandler<TEntity> Created;

        /// <summary>
        /// 
        /// </summary>
        /// <param name="e"></param>
        protected virtual void OnCreated(ActionEventArgs<TEntity> e)
        {
            ActionEventHandler<TEntity> handler = this.Created;

            if (handler != null)
            {
                handler(this, e);
            }
        }

        #endregion

        #region Insert

        /// <summary>
        /// 
        /// </summary>
        public event AccessEventHandler CanInsert;

        /// <summary>
        /// 
        /// </summary>
        /// <param name="e"></param>
        protected virtual void OnCanInsert(AccessEventArgs e)
        {
            AccessEventHandler handler = this.CanInsert;

            if (handler != null)
            {
                handler(this, e);
            }
        }

        /// <summary>
        /// 
        /// </summary>
        public event ActionEventHandler<TEntity> Inserting;

        /// <summary>
        /// 
        /// </summary>
        /// <param name="e"></param>
        protected virtual void OnInserting(ActionEventArgs<TEntity> e)
        {
            ActionEventHandler<TEntity> handler = this.Inserting;

            if (handler != null)
            {
                handler(this, e);
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="entity"></param>
        /// <returns></returns>
        /// <exception cref="System.Data.Entity.Extensions.AccessException"></exception>
        public virtual TEntity Insert(TEntity entity)
        {
            if (this.CheckAccess(this.OnCanInsert))
            {
                try
                {
                    this.OnInserting(new ActionEventArgs<TEntity>(entity));

                    entity = this.Set.Add(entity);
                    this.UnitOfWork.Commit();

                    this.OnInserted(new ActionEventArgs<TEntity>(entity));
                }
                catch (Exception)
                {
                    throw;
                }
            }
            else
            {
                throw new AccessException("CanInsert");
            }

            return entity;
        }

        /// <summary>
        /// 
        /// </summary>
        public event ActionEventHandler<TEntity> Inserted;

        /// <summary>
        /// 
        /// </summary>
        /// <param name="e"></param>
        protected virtual void OnInserted(ActionEventArgs<TEntity> e)
        {
            ActionEventHandler<TEntity> handler = this.Inserted;

            if (handler != null)
            {
                handler(this, e);
            }
        }

        #endregion

        #region Select

        /// <summary>
        /// 
        /// </summary>
        public event AccessEventHandler CanRead;

        /// <summary>
        /// 
        /// </summary>
        /// <param name="e"></param>
        protected virtual void OnCanRead(AccessEventArgs e)
        {
            AccessEventHandler handler = this.CanRead;

            if (handler != null)
            {
                handler(this, e);
            }
        }

        /// <summary>
        /// 
        /// </summary>
        public event FilterEventHandler<TEntity> Filter;

        /// <summary>
        /// 
        /// </summary>
        /// <param name="e"></param>
        protected virtual void OnFilter(FilterEventArgs<TEntity> e)
        {
            FilterEventHandler<TEntity> handler = this.Filter;

            if (handler != null)
            {
                handler(this, e);
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        public virtual TEntity GetByKey(object key)
        {
            if (this.CheckAccess(this.OnCanRead))
            {
                try
                {
                    try
                    {
                        return this.Set.Find(key);
                    }
                    catch (Exception)
                    {
                        throw;
                    }
                }
                catch (Exception)
                {
                    throw;
                }
            }
            else
            {  
                throw new AccessException("CanRead");
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="keyValues"></param>
        /// <returns></returns>
        /// <exception cref="System.InvalidOperationException"></exception>
        /// <exception cref="System.Data.Entity.Extensions.AccessException"></exception>
        public virtual TEntity GetByKeys(params object[] keyValues)
        {
            if (this.CheckAccess(this.OnCanRead))
            {
                try
                {
                    return this.Set.Find(keyValues);
                }
                catch (Exception)
                {
                    throw;
                }
            }
            else
            {
                throw new AccessException("CanRead");
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        /// <exception cref="System.ArgumentNullException"></exception>
        /// <exception cref="System.Data.Entity.Extensions.AccessException"></exception>
        public virtual IEnumerable<TEntity> Get()
        {
            try
            {
                return this.Get(null, null);
            }
            catch (Exception)
            {
                throw;
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="filter"></param>
        /// <returns></returns>
        /// <exception cref="System.ArgumentNullException"></exception>
        /// <exception cref="System.Data.Entity.Extensions.AccessException"></exception>
        public virtual IEnumerable<TEntity> Get(Func<TEntity, bool> filter)
        {
            try
            {
                return this.Get(filter, null);
            }
            catch (Exception)
            {
                throw;
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="order"></param>
        /// <returns></returns>
        /// <exception cref="System.ArgumentNullException"></exception>
        /// <exception cref="System.Data.Entity.Extensions.AccessException"></exception>
        public virtual IEnumerable<TEntity> Get(Func<IEnumerable<TEntity>, IOrderedEnumerable<TEntity>> order)
        {
            try
            {
                return this.Get(null, order);
            }
            catch (Exception)
            {
                throw;
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="filter"></param>
        /// <param name="order"></param>
        /// <returns></returns>
        /// <exception cref="System.ArgumentNullException"></exception>
        /// <exception cref="System.Data.Entity.Extensions.AccessException"></exception>
        public virtual IEnumerable<TEntity> Get(Func<TEntity, bool> filter, Func<IEnumerable<TEntity>, IOrderedEnumerable<TEntity>> order)
        {
            if (this.CheckAccess(this.OnCanRead))
            {
                try
                {
                    IEnumerable<TEntity> result = this.Set.Where(FilterEntity);

                    if (filter != null)
                    {
                        result = result.Where(filter);
                    }

                    if (order == null)
                    {
                        return result.ToList();
                    }
                    else
                    {                        
                        return order(result).ToList();
                    }
                }
                catch (Exception)
                {
                    throw;
                }
            }
            else
            {
                throw new AccessException("CanRead");
            }
        }
        
        #endregion

        #region Update

        /// <summary>
        /// 
        /// </summary>
        public event AccessEventHandler CanUpdate;

        /// <summary>
        /// 
        /// </summary>
        /// <param name="e"></param>
        protected virtual void OnCanUpdate(AccessEventArgs e)
        {
            AccessEventHandler handler = this.CanUpdate;

            if (handler != null)
            {
                handler(this, e);
            }
        }

        /// <summary>
        /// 
        /// </summary>
        public event ActionEventHandler<TEntity> Updating;

        /// <summary>
        /// 
        /// </summary>
        /// <param name="e"></param>
        protected virtual void OnUpdating(ActionEventArgs<TEntity> e)
        {
            ActionEventHandler<TEntity> handler = this.Updating;

            if (handler != null)
            {
                handler(this, e);
            }
        }

        /// <summary>
        /// /
        /// </summary>
        /// <param name="entity"></param>
        /// <returns></returns>
        /// <exception cref="System.Data.Entity.Extensions.AccessException"></exception>
        public virtual TEntity Update(TEntity entity)
        {
            if (this.CheckAccess(this.OnCanUpdate))
            {
                try
                {
                    this.OnUpdating(new ActionEventArgs<TEntity>(entity));

                    entity = this.Set.Attach(entity);
                    this.Context.Entry(entity).State = EntityState.Modified;

                    this.UnitOfWork.Commit();

                    this.OnUpdated(new ActionEventArgs<TEntity>(entity));

                    return entity;
                }
                catch (Exception)
                {
                    throw;
                }
            }
            else
            {
                throw new AccessException("CanUpdate");
            }
        }

        /// <summary>
        /// 
        /// </summary>
        public event ActionEventHandler<TEntity> Updated;

        /// <summary>
        /// 
        /// </summary>
        /// <param name="e"></param>
        protected virtual void OnUpdated(ActionEventArgs<TEntity> e)
        {
            ActionEventHandler<TEntity> handler = this.Updated;

            if (handler != null)
            {
                handler(this, e);
            }
        }

        #endregion

        #region Delete

        /// <summary>
        /// 
        /// </summary>
        public event AccessEventHandler CanDelete;

        /// <summary>
        /// 
        /// </summary>
        /// <param name="e"></param>
        protected virtual void OnCanDelete(AccessEventArgs e)
        {
            AccessEventHandler handler = this.CanDelete;

            if (handler != null)
            {
                handler(this, e);
            }
        }

        /// <summary>
        /// 
        /// </summary>
        public event ActionEventHandler<TEntity> Deleting;

        /// <summary>
        /// 
        /// </summary>
        /// <param name="e"></param>
        protected virtual void OnDeleting(ActionEventArgs<TEntity> e)
        {
            ActionEventHandler<TEntity> handler = this.Deleting;

            if (handler != null)
            {
                handler(this, e);
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="keyValue"></param>
        /// <returns></returns>
        public virtual TEntity Delete(object keyValue)
        {
            try
            {
                TEntity entity = this.Set.Find(keyValue);

                return this.Delete(entity);
            }
            catch (Exception)
            {
                throw;
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="keyValues"></param>
        /// <returns></returns>
        /// <exception cref="System.InvalidOperationException"></exception>
        /// <exception cref="System.Data.Entity.Extensions.AccessException"></exception>
        public virtual TEntity Delete(params object[] keyValues)
        {
            try
            {
                TEntity entity = this.Set.Find(keyValues);

                return this.Delete(entity);
            }
            catch (Exception)
            {
                throw;
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="entity"></param>
        /// <returns></returns>
        /// <exception cref="System.Data.Entity.Extensions.AccessException"></exception>
        public virtual TEntity Delete(TEntity entity)
        {
            if (this.CheckAccess(this.OnCanDelete))
            {
                try
                {
                    if (this.Context.Entry(entity).State == EntityState.Detached)
                    {
                        this.Set.Attach(entity);
                    }

                    this.OnDeleting(new ActionEventArgs<TEntity>(entity));

                    entity = this.Set.Remove(entity);

                    this.UnitOfWork.Commit();

                    this.OnDeleted(new ActionEventArgs<TEntity>(entity));

                    return entity;
                }
                catch (Exception)
                {
                    throw;
                }
            }
            else
            {
                throw new AccessException("CanDelete");
            }
        }
        
        /// <summary>
        /// 
        /// </summary>
        public event ActionEventHandler<TEntity> Deleted;

        /// <summary>
        /// 
        /// </summary>
        /// <param name="e"></param>
        protected virtual void OnDeleted(ActionEventArgs<TEntity> e)
        {
            ActionEventHandler<TEntity> handler = this.Deleted;

            if (handler != null)
            {
                handler(this, e);
            }
        }

        #endregion
    
        #region Helper

        /// <summary>
        /// 
        /// </summary>
        /// <param name="accessAction"></param>
        /// <returns></returns>
        /// <exception cref="System.ArgumentNullException"></exception>
        protected bool CheckAccess(Action<AccessEventArgs> accessAction)
        {
            if (accessAction == null)
            {
                throw new ArgumentNullException("accessAction");
            }

            bool haveAccess = true;
            AccessEventArgs accessEventArgs = new AccessEventArgs(haveAccess);
         
            try
            {
                accessAction.Invoke(accessEventArgs);
            }
            catch (Exception)
            {
                throw;
            }

            return accessEventArgs.Result;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="entity"></param>
        /// <returns></returns>
        protected bool FilterEntity(TEntity entity)
        {
            bool result = true;
            FilterEventArgs<TEntity> filterEventArgs = new FilterEventArgs<TEntity>(entity, result);
                
            try
            {
                this.OnFilter(filterEventArgs);
            }
            catch (Exception)
            {
                throw;
            }

            return filterEventArgs.Result;
        }

        #endregion

        #endregion
    }
}
